﻿namespace EFS.RC.CC.Sessions.OfficeService.Customers.HuntQueue
{
	/// <summary>
	/// Customer session
	/// </summary>
	internal class DialIn : EFS.RC.CC.Sessions.OfficeService.Customers.Customer
	{
		#region Composition

		// Hunter
		private EFS.RC.CC.Tools.Hunters.Agent agentHunter = null;

		/// Office conversation
		private Microsoft.Rtc.Collaboration.Conversation officeConversation;

		// Customer call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundCustomerAudioVideoCall = null;

		// office call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall officeConversationAudioVideoCall = null;

		// back to back call into office conversation
		private Microsoft.Rtc.Collaboration.BackToBackCall officeConversationB2BCall;

		// Display Uri
		private System.String displayUri = null;

		// Customer Uri
		private System.String sourceUri = null;

		// Impersonated uri
		private System.String participantEndPointUri = null;

		// Impersonated display name
		private System.String impersonatedDisplayName = null;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="customersOfficeServiceEndPointApplication"></param>
		/// <param name="inboundCustomerAudioVideoCall"></param>
		public DialIn(EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication, Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundCustomerAudioVideoCall, Microsoft.Rtc.Signaling.FromToHeader fromHeader)
			:base(customersOfficeServiceEndPointApplication)
		{
			// check
			EFS.Common.Verify.Reference(customersOfficeServiceEndPointApplication, "customersOfficeServiceEndPointApplication");
			EFS.Common.Verify.Reference(fromHeader, "fromHeader");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "DialIn:ctor", fromHeader.Uri, GetType()); 

			// set locals
			this.inboundCustomerAudioVideoCall = inboundCustomerAudioVideoCall;
			this.sourceUri = fromHeader.Uri;

			// make display uri
			displayUri = EFS.RC.Common.Utilities.SipUriCleanup(this.sourceUri); 

			// make display name
			if (EFS.Common.Verify.String(fromHeader.DisplayName))
			{
				impersonatedDisplayName = fromHeader.DisplayName;
			}
			else if (EFS.Common.Verify.String(displayUri))
			{
				impersonatedDisplayName = displayUri;
			}
			else
			{
				impersonatedDisplayName = "";
			}

			// make impersonated uri
			participantEndPointUri = System.String.Format("sip:{0}@{1}", ConversationGuid, customersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint.DefaultDomain);

			// state change
			inboundCustomerAudioVideoCall.Conversation.StateChanged += OnCustomerConversationStateChanged;
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format(System.String.Format("EFS.RC.CC.Sessions.OfficeService.Customers.HuntQueue.DialIn {0}", CustomerUri));
		}

		/// <summary>
		/// Impersonated display name
		/// </summary>
		public override System.String DisplayName
		{
			get { return impersonatedDisplayName; }
		}

		/// <summary>
		/// Impersonated uri
		/// </summary>
		public override System.String ParticipantEndPointUri
		{
			get { return participantEndPointUri; }
		}

		/// <summary>
		/// Customer uri
		/// </summary>
		public override System.String CustomerUri
		{
			get { return sourceUri; }
		}

		/// Display uri
		/// </summary>
		public override System.String DisplayUri
		{
			get { return displayUri; }
		}

		/// <summary>
		/// Check if hunter
		/// </summary>
		public System.Boolean IsHunted
		{
			get
			{
				// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
				lock (syncAccess)
				{
					return (Status != SessionStatus.Terminated) && (null != agentHunter);
				}
			}
		}

		#endregion

		#region Startup

		/// <summary>
		/// Mark start time
		/// </summary>
		/// <returns></returns>
		protected override System.DateTime MarkStart()
		{
			return EFS.RC.CC.Tools.Loggers.CHL.Add(ConversationGuid, CustomersOfficeServiceEndPointApplication.ApplicationPresence.PresenceEndPointDeployment.ID.Value, DisplayUri, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Customer_StartIn);
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		protected override void SetupSession()
		{
			EFS.EventLog.Log.LogInfo("SetupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "SetupSession", this); 

			// make b2b call
			BeginAsyncProcess2(JoinOfficeConference);
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void JoinOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "JoinOfficeConference", this); 

			// get conference uri
			System.String conferenceUri = OfficeServiceConference.ConferenceUri;

			// get end point
			Microsoft.Rtc.Collaboration.ApplicationEndpoint applicationEndpoint = CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate officeConversation {0}", ParticipantEndPointUri), this);

				// make office conversation
				officeConversation = new Microsoft.Rtc.Collaboration.Conversation(applicationEndpoint);
				officeConversation.Impersonate(ParticipantEndPointUri, null, DisplayUri);
				officeConversation.ApplicationContext = this;
				officeConversation.StateChanged += OnOfficeConversationStateChanged;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.TrustedParticipant;

				// join
				officeConversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeConference, null));
			}
		}

		/// <summary>
		/// End join
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "EndJoinOfficeConference", this);

			try
			{
				// end join
				officeConversation.ConferenceSession.EndJoin(ar);

				// make back2back call
				BeginAsyncProcess2(MakeB2BCall);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void MakeB2BCall()
		{
			// log
			EFS.EventLog.Log.LogInfo("MakeB2BCall", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "MakeB2BCall", this); 

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// Make B2B
				officeConversationAudioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(officeConversation);

				// make call
				officeConversationB2BCall = new Microsoft.Rtc.Collaboration.BackToBackCall(
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(inboundCustomerAudioVideoCall),
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(officeConversationAudioVideoCall));

				// start call
				officeConversationB2BCall.BeginEstablish(
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(OnEndEstablishB2BCall));

			}
		}

		/// <summary>
		/// End B2B call
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndEstablishB2BCall(System.IAsyncResult ar)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnEndEstablishB2BCall", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnEndEstablishB2BCall", this); 

			try
			{
				// end
				officeConversationB2BCall.EndEstablish(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Participant added
		/// </summary>
		protected override void ParticipantEndPointAdded()
		{
			// call base
			base.ParticipantEndPointAdded();

			// get agent
			BeginAsyncProcess2(BringAgentIntoOfficeServiceConference);
		}

		/// <summary>
		/// Bring agent
		/// </summary>
		/// <param name="ar"></param>
		protected void BringAgentIntoOfficeServiceConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("BringAgentIntoOfficeServiceConference", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "BringAgentIntoOfficeServiceConference", this); 

			lock (syncAccess)
			{
				// check
				if (null != agentHunter)
				{
					throw new System.InvalidOperationException("Agent Hunter is already started");
				}

				// check handler
				switch (EFS.RC.CC.Config.Scheduler.Settings.Current.GetCurrentHandler)
				{
					case Config.Scheduler.TimeInterval.Handlers.Hunt:
						{
							try
							{
								// hunt for agent
								agentHunter = new EFS.RC.CC.Tools.Hunters.Agent(this, CustomersOfficeServiceEndPointApplication.OfficeServiceOperatorsEndPointApplication.OperatorsTeam.AgentsHunterQueue);
								agentHunter.AsinkTerminatedDelegate.Register(OnHunterTerminated);
							}
							catch (System.Exception ex)
							{
								// log
								EFS.EventLog.Log.LogException(ex, this);

								// remove hunter
								this.agentHunter = null;

								// shut it down
								BeginShutdown();

								break;
							}

							// start music for hunting
							BeginStartHuntingPlayer();

							// break
							break;
						}
					case Config.Scheduler.TimeInterval.Handlers.AfterHours:
						{
							// start music for hunting
							BeginStartAfterHoursPlayer();

							// break
							break;
						}
				}
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Outside call
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall OutsideAudioVideoCall
		{
			get { return inboundCustomerAudioVideoCall; }
		}

		/// <summary>
		/// Inside call
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall InsideAudioVideoCall
		{
			get { return officeConversationAudioVideoCall; }
		}

		/// <summary>
		/// Office conversation
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.Conversation OfficeConversation
		{
			get { return officeConversation; }
		}

		/// <summary>
		/// State changed for customer conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnCustomerConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnCustomerConversationStateChanged", this);
			try
			{
				// SAL
				EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnCustomerConversationStateChanged", args.State, this); 

				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - customer conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}	
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnOfficeConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOfficeConversationStateChanged", this);
	
			try
			{
				// SAL
				EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnOfficeConversationStateChanged", args.State, this); 

				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}	
		}

		/// <summary>
		/// Agent session terminated
		/// </summary>
		private void OnHunterTerminated(EFS.RC.CC.Tools.Hunters.Agent agentHunter)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnHunterTerminated", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnHunterTerminated", this); 

			// lock access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check
				if (null != this.agentHunter && !this.agentHunter.Equals(agentHunter))
				{
					throw new System.InvalidOperationException("Agent Hunter is different then terminated");
				}

				// remove agent hunter
				this.agentHunter = null;
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// <summary>
		/// Remove customer from office conference
		/// </summary>
		protected override void CleanupSession()
		{
			// log
			EFS.EventLog.Log.LogInfo("CleanupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "CleanupSession", this);

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// remove delegate
			officeConversation.StateChanged -= OnOfficeConversationStateChanged;

			// terminate all
			termination.Terminate(officeConversationB2BCall);
		}

		#endregion
	}
}
